/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "pin_auth_core_inner.h"

#include <stddef.h>

#include "securec.h"

#include "apdu_core_defines.h"
#include "apdu_utils.h"
#include "card_channel.h"
#include "logger.h"
#include "parcel.h"

#define PIN_AUTH_STATUS_LEN 1
#define PIN_AUTH_FAIL_CNT_LEN 2
#define PIN_AUTH_PUNISH_TIME_LEN 4
#define PIN_AUTH_DESTRUCT_CNT_LEN 2

#define PIN_AUTH_FAILED_RESULT_LEN \
    (PIN_AUTH_STATUS_LEN + PIN_AUTH_FAIL_CNT_LEN + PIN_AUTH_PUNISH_TIME_LEN + PIN_AUTH_DESTRUCT_CNT_LEN)

ResultCode PinAuthEnrollLocalFromRspApdu(const ResponseApdu *apdu, PinDataSecret *secret)
{
    if (apdu == NULL || secret == NULL) {
        return INVALID_PARA_NULL_PTR;
    }
    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    if (!GetResponseBufferValueAt(apdu, 0, (uint8_t *)secret, sizeof(PinDataSecret))) {
        LOG_ERROR("GetResponseUint32ValueAt failed");
        return RES_APDU_DATA_ERR;
    }

    return SUCCESS;
}

ResultCode PinAuthGetFreezeStatusFormFromRspApdu(const ResponseApdu *apdu, uint32_t startIndex, PinFreezeStatus *status)
{
    if (apdu == NULL || status == NULL) {
        return INVALID_PARA_NULL_PTR;
    }
    ResultCode ret = SUCCESS;

    uint32_t offset = startIndex;
    do {
        if (!GetResponseUint16ValueAt(apdu, offset, &status->failCnt)) {
            LOG_ERROR("GetResponseUint16ValueAt failCnt failed");
            ret = RES_APDU_DATA_ERR_INDEX_1;
            break;
        }
        offset += PIN_AUTH_FAIL_CNT_LEN;
        if (!GetResponseUint32ValueAt(apdu, offset, &status->punishTime)) {
            LOG_ERROR("GetResponseUint32ValueAt punishTime failed");
            ret = RES_APDU_DATA_ERR_INDEX_2;
            break;
        }
        offset += PIN_AUTH_PUNISH_TIME_LEN;
        if (!GetResponseUint16ValueAt(apdu, offset, &status->destructCnt)) {
            LOG_ERROR("GetResponseUint16ValueAt destructCnt failed");
            ret = RES_APDU_DATA_ERR_INDEX_3;
            break;
        }
    } while (0);

    return ret;
}

ResultCode PinAuthAuthenticationLocalFromRspApdu(const ResponseApdu *apdu, PinAuthResult *result)
{
    if (apdu == NULL || result == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    uint32_t size = 0;
    const uint8_t *data = GetResponseDataBuffer(apdu, &size);
    if (data == NULL || size == 0) {
        return RES_APDU_FMT_ERR;
    }

    ResultCode ret = SUCCESS;
    result->status = data[0]; // the first bit is the auth status
    switch (result->status) {
        case SE_PIN_AUTH_STATUS_OK:
            /* 1 byte status, 32 bytes secret value */
            if (size != PIN_AUTH_STATUS_LEN + PIN_DATA_SECRET_LEN) {
                LOG_ERROR("size value is not ok, curr = %u", size);
                ret = RES_APDU_DATA_ERR;
                break;
            }

            if (!GetResponseBufferValueAt(apdu, 1, result->secret.data, PIN_DATA_SECRET_LEN)) {
                ret = RES_APDU_DATA_ERR_INDEX_1;
                break;
            }
            break;
        case SE_PIN_AUTH_STATUS_FAIL:     // fallthrough
        case SE_PIN_AUTH_STATUS_PUNISH:   // fallthrough
        case SE_PIN_AUTH_STATUS_DESTRUCT: // fallthrough
        default:
            /* 1 byte status, 2 bytes fail cnt,  4 bytes punish time, 2 bytes destruct time */
            if (size != PIN_AUTH_FAILED_RESULT_LEN) {
                LOG_ERROR("size value is not ok, curr = %u", size);
                ret = RES_APDU_DATA_ERR;
                break;
            }

            if (PinAuthGetFreezeStatusFormFromRspApdu(apdu, 1, &result->freeze) != SUCCESS) {
                LOG_ERROR("size value is not ok, curr = %u", size);
                ret = RES_APDU_DATA_ERR;
                break;
            }
    }
    return ret;
}

ResultCode PinAuthGetNumSlotsFromRspApdu(const ResponseApdu *apdu, uint32_t *numSlots)
{
    if (apdu == NULL || numSlots == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    if (!GetResponseUint32ValueAt(apdu, 0, numSlots)) {
        LOG_ERROR("GetResponseUint32ValueAt failed");
        return RES_APDU_DATA_ERR;
    }

    return SUCCESS;
}

ResultCode PinAuthCmdSetSelfDestructEnableFromRspApdu(const ResponseApdu *apdu, PinConfigResult *configResult)
{
    if (apdu == NULL || configResult == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    uint32_t size = 0;
    const uint8_t *data = GetResponseDataBuffer(apdu, &size);
    if (data == NULL || size == 0) {
        return RES_APDU_FMT_ERR;
    }

    configResult->freeze.failCnt = 0;
    configResult->freeze.destructCnt = 0;
    configResult->freeze.punishTime = 0;
    configResult->status = data[0]; // the first bit is the auth status

    switch (configResult->status) {
        case SE_PIN_CONFIG_STATUS_OK:           // fallthrough
        case SE_PIN_CONFIG_STATUS_FAIL_NO_PERM: // fallthrough
            return SUCCESS;
        default:
            return PinAuthGetFreezeStatusFormFromRspApdu(apdu, 1, &configResult->freeze);
    }
}

ResultCode PinAuthCmdSetSelfDestructEnableToCmdApduData(uint32_t slotId, const PinDestructConfig *config,
    const PinDataHash *hash, uint8_t *body, uint32_t *size)
{
    if (config == NULL || hash == NULL) {
        LOG_ERROR("input key or area or data is nullptr");
        return INVALID_PARA_NULL_PTR;
    }

    if (body == NULL || size == NULL) {
        LOG_ERROR("input body or size nullptr");
        return INVALID_PARA_NULL_PTR;
    }

    ResultCode ret = MEM_COPY_ERR;

    Parcel parcel = CreateParcel(*size);
    do {
        uint32_t currSlot = slotId;
        DataRevert((uint8_t *)&currSlot, sizeof(uint32_t));
        if (!ParcelWriteUint32(&parcel, currSlot)) {
            break;
        }

        if (!ParcelWriteUint8(&parcel, config->enable ? 1 : 0)) {
            break;
        }

        uint16_t currDestructMaxCnt = config->destructMaxCnt;
        DataRevert((uint8_t *)&currDestructMaxCnt, sizeof(uint16_t));
        if (!ParcelWriteUint16(&parcel, currDestructMaxCnt)) {
            break;
        }

        if (!ParcelWrite(&parcel, hash->data, PIN_DATA_HASH_LEN)) {
            break;
        }

        if (!ParcelToDataBuffer(&parcel, body, size)) {
            break;
        }
        ret = SUCCESS;
    } while (0);

    DeleteParcel(&parcel);
    return ret;
}

ResultCode PinAuthCmdSetFreezePolicyToCmdApduData(uint16_t punishStartCnt, uint16_t destructMaxCnt,
    uint32_t enableDestructDefault, uint8_t *body, uint32_t *size)
{
    ResultCode ret = MEM_COPY_ERR;

    Parcel parcel = CreateParcel(*size);
    do {
        uint16_t currPunishStartCnt = punishStartCnt;
        DataRevert((uint8_t *)&currPunishStartCnt, sizeof(uint16_t));
        if (!ParcelWriteUint16(&parcel, currPunishStartCnt)) {
            break;
        }

        uint16_t currDestructMaxCnt = destructMaxCnt;
        DataRevert((uint8_t *)&currDestructMaxCnt, sizeof(uint16_t));
        if (!ParcelWriteUint16(&parcel, currDestructMaxCnt)) {
            break;
        }

        if (!ParcelWriteUint8(&parcel, enableDestructDefault ? 1 : 0)) {
            break;
        }

        if (!ParcelToDataBuffer(&parcel, body, size)) {
            break;
        }
        ret = SUCCESS;
    } while (0);

    DeleteParcel(&parcel);
    return ret;
}

ResultCode PinAuthCmdSetFreezePolicyFromRspApdu(const ResponseApdu *apdu, uint8_t *status)
{
    if (apdu == NULL || status == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!GetResponseUint8ValueAt(apdu, 0, status)) {
        LOG_ERROR("GetResponseBufferValueAt status failed");
        return RES_APDU_DATA_ERR_INDEX_1;
    }

    LOG_INFO("status is %d", (uint32_t)(*status));
    return SUCCESS;
}

ResultCode PinAuthCmdGetFreezePolicyFromRspApdu(const ResponseApdu *apdu, uint16_t *punishStartCnt,
    uint16_t *destructMaxCnt, uint32_t *enableDestructDefault)
{
    if (apdu == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (punishStartCnt == NULL || destructMaxCnt == NULL || enableDestructDefault == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    ResultCode ret = RES_APDU_DATA_ERR;

    uint32_t offset = 0;
    do {
        if (!GetResponseUint16ValueAt(apdu, offset, punishStartCnt)) {
            LOG_ERROR("GetResponseBufferValueAt punishStartCnt failed");
            ret = RES_APDU_DATA_ERR_INDEX_1;
            break;
        }
        offset += sizeof(uint16_t);
        if (!GetResponseUint16ValueAt(apdu, offset, destructMaxCnt)) {
            LOG_ERROR("GetResponseBufferValueAt destructMaxCnt failed");
            ret = RES_APDU_DATA_ERR_INDEX_2;
            break;
        }

        offset += sizeof(uint16_t);
        uint8_t enable = 0;
        if (!GetResponseUint8ValueAt(apdu, offset, &enable)) {
            LOG_ERROR("GetResponseBufferValueAt enableDestructDefault failed");
            ret = RES_APDU_DATA_ERR_INDEX_3;
            break;
        }
        *enableDestructDefault = enable;
        ret = SUCCESS;
    } while (0);

    return ret;
}

ResultCode ProcPinAuthCmdAuthRemotePrepareToCmdApduData(uint32_t slotId, uint64_t session, uint8_t *body,
    uint32_t *size)
{
    ResultCode ret = MEM_COPY_ERR;

    Parcel parcel = CreateParcel(*size);
    do {
        uint32_t currSlotId = slotId;
        DataRevert((uint8_t *)&currSlotId, sizeof(uint32_t));
        if (!ParcelWriteUint32(&parcel, currSlotId)) {
            break;
        }

        uint64_t currSession = session;
        DataRevert((uint8_t *)&currSession, sizeof(uint64_t));
        if (!ParcelWriteUint64(&parcel, currSession)) {
            break;
        }

        if (!ParcelToDataBuffer(&parcel, body, size)) {
            break;
        }
        ret = SUCCESS;
    } while (0);

    DeleteParcel(&parcel);
    return ret;
}

ResultCode ProcPinAuthCmdAuthRemotePrepareFromRspApdu(const ResponseApdu *apdu,
    PinDataRemoteServiceChallenge *challenge)
{
    if (apdu == NULL || challenge == NULL) {
        return INVALID_PARA_NULL_PTR;
    }
    ResultCode ret = RES_APDU_DATA_ERR;

    uint32_t offset = 0;
    do {
        if (!GetResponseBufferValueAt(apdu, offset, challenge->pk.data, sizeof(challenge->pk.data))) {
            LOG_ERROR("GetResponseBufferValueAt pk failed");
            ret = RES_APDU_DATA_ERR_INDEX_1;
            break;
        }
        offset += sizeof(challenge->pk.data);
        if (!GetResponseBufferValueAt(apdu, offset, challenge->nonce.data, sizeof(challenge->nonce.data))) {
            LOG_ERROR("GetResponseBufferValueAt nonce failed");
            ret = RES_APDU_DATA_ERR_INDEX_2;
            break;
        }
        ret = SUCCESS;
    } while (0);

    return ret;
}

ResultCode ProcPinAuthCmdAuthRemoteProcessToCmdApduData(uint32_t slotId, uint64_t session,
    const PinDataRemoteClientProof *proof, uint8_t *body, uint32_t *size)
{
    ResultCode ret = MEM_COPY_ERR;

    Parcel parcel = CreateParcel(*size);
    do {
        uint32_t currSlotId = slotId;
        DataRevert((uint8_t *)&currSlotId, sizeof(uint32_t));
        if (!ParcelWriteUint32(&parcel, currSlotId)) {
            break;
        }

        uint64_t currSession = session;
        DataRevert((uint8_t *)&currSession, sizeof(uint64_t));
        if (!ParcelWriteUint64(&parcel, currSession)) {
            break;
        }

        if (!ParcelWrite(&parcel, proof->pk.data, sizeof(proof->pk.data))) {
            break;
        }

        if (!ParcelWrite(&parcel, proof->nonce.data, sizeof(proof->nonce.data))) {
            break;
        }

        if (!ParcelWrite(&parcel, proof->hmac.data, sizeof(proof->hmac.data))) {
            break;
        }

        if (!ParcelToDataBuffer(&parcel, body, size)) {
            break;
        }
        ret = SUCCESS;
    } while (0);

    DeleteParcel(&parcel);
    return ret;
}

ResultCode PinAuthAuthenticationRemoteFromRspApdu(const ResponseApdu *apdu, PinAuthResult *result)
{
    if (apdu == NULL || result == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    uint32_t size = 0;
    const uint8_t *data = GetResponseDataBuffer(apdu, &size);
    if (data == NULL || size == 0) {
        return RES_APDU_FMT_ERR;
    }

    ResultCode ret = SUCCESS;
    result->status = data[0]; // the first bit is the auth status
    switch (result->status) {
        case SE_PIN_AUTH_STATUS_OK:
            /* only 1 byte status in this case */
            if (size != PIN_AUTH_STATUS_LEN) {
                LOG_ERROR("size value is not ok, curr = %u", size);
                ret = RES_APDU_DATA_ERR;
                break;
            }
            (void)memset_s(result, sizeof(PinAuthResult), 0, sizeof(PinAuthResult));
            result->status = data[0];
            break;
        case SE_PIN_AUTH_STATUS_FAIL:     // fallthrough
        case SE_PIN_AUTH_STATUS_PUNISH:   // fallthrough
        case SE_PIN_AUTH_STATUS_DESTRUCT: // fallthrough
        default:
            /* 1 byte status, 2 bytes fail cnt,  4 bytes punish time, 2 bytes destruct time */
            if (size != PIN_AUTH_FAILED_RESULT_LEN) {
                LOG_ERROR("size value is not ok, curr = %u", size);
                ret = RES_APDU_DATA_ERR;
                break;
            }

            if (PinAuthGetFreezeStatusFormFromRspApdu(apdu, 1, &result->freeze) != SUCCESS) {
                LOG_ERROR("size value is not ok, curr = %u", size);
                ret = RES_APDU_DATA_ERR;
                break;
            }
    }
    return ret;
}

ResultCode ProcPinAuthCmdAuthRemoteAbortToCmdApduData(uint32_t slotId, uint64_t session, uint8_t *body, uint32_t *size)
{
    // same as prepare
    return ProcPinAuthCmdAuthRemotePrepareToCmdApduData(slotId, session, body, size);
}